
public abstract class SingleLinkedListTest {
	void testNewInstanceIsEmpty() throws TestException {
		SingleLinkedList list = newInstance();
		if (!list.isEmpty()) {
			throw new TestException("New instance of list is expected to be empty");
		}
	}
		
	void testInsert() throws TestException {
		SingleLinkedList list = newInstance();
		list.insertLast("2");
		list.insertFirst("0");
		list.insert("1", 1);
		if (!"0".equals(list.get(0))) {
			throw new TestException("Wrong value at index 0, expected:0 actual:"+list.get(0));
		}
		if (!"2".equals(list.get(2))) {
			throw new TestException("Wrong value at index 2, expected:2 actual:"+list.get(2));
		}
		if (!"1".equals(list.get(1))) {
			throw new TestException("Wrong value at index 1, expected:1 actual:"+list.get(1));
		}
	}

	void testRemove() throws TestException {
		SingleLinkedList list = newInstance();
		list.insertFirst("0");
		list.remove(0);
		if (!list.isEmpty()) {
			throw new TestException("[0] + remove(0) == []");
		}

		list.insertLast("3");
		list.insertLast("2");
		list.insertLast("1");
		list.remove(1);
		if (!"1".equals(list.get(1))) {
			throw new TestException("[3,2,1] + remove(1) == [3,1]");
		}

		list.remove(0);
		if (!"1".equals(list.get(0))) {
			throw new TestException("[3,1] + remove(0) == [1]");
		}

		list.remove(0);
		if (!list.isEmpty()) {
			throw new TestException("[1] + remove(0) == []");
		}
	}

	void testClear() throws TestException {
		SingleLinkedList list = newInstance();
		list.insertFirst("3");
		list.insertFirst("2");
		list.insertFirst("1");
		list.clear();
		if (!list.isEmpty()) {
			throw new TestException("[1,2,3] + clear() == []");
		}
	}

	void testSwap() throws TestException {
		SingleLinkedList list = newInstance();
		list.insertFirst("1");
		list.swap(0, 0);
		if (!"1".equals(list.get(0))) {
			throw new TestException("[1].swap(0,0) == [1]");
		}
		list.insertFirst("2");
		list.swap(0, 1);
		if (!"1".equals(list.get(0))) {
			throw new TestException("[2,1].swap(0,1) == [1,2]");
		}
		if (!"2".equals(list.get(1))) {
			throw new TestException("[2,1].swap(0,1) == [1,2]");
		}
	}
	
	void testSize() throws TestException {
		SingleLinkedList list = newInstance();
		if (list.size()!=0) {
			throw new TestException("empty list must have size=0. actual size="+list.size());
		}
		list.insertLast("1");
		if (list.size()!=1) {
			throw new TestException("list [1] must have size=1. actual size="+list.size());
		}
		list.insertFirst("2");
		if (list.size()!=2) {
			throw new TestException("list [2,1] must have size=2. actual size="+list.size());
		}
		list.insert("3", 1);
		if (list.size()!=3) {
			throw new TestException("list [2,3,1] must have size=3. actual size="+list.size());
		}
		list.remove(0);
		if (list.size()!=2) {
			throw new TestException("list [3,1] must have size=2. actual size="+list.size());
		}
		list.clear();
		if (list.size()!=0) {
			throw new TestException("empty list must have size=0. actual size="+list.size());
		}		
	}
	
	void testOutOfBounds() {
		SingleLinkedList list = newInstance();
		{
			boolean caught = false;
			try {
				list.remove(0);
			}
			catch (IndexOutOfBoundsException e) {
				caught = true;
			}
			if (!caught) {
				throw new RuntimeException("removing from empty list should throw exception");
			}
		}
		{
			boolean caught = false;
			try {
				list.insert("1", 10);
			}
			catch (IndexOutOfBoundsException e) {
				caught = true;
			}
			if (!caught) {
				throw new RuntimeException("inserting into wrong position should throw exception");
			}
			if (!list.isEmpty() || list.size()>0) {
				throw new RuntimeException("inserting into wrong position should not change list size");
			}
		}
		{
			boolean caught = false;
			try {
				list.insert("1", -10);
			}
			catch (IndexOutOfBoundsException e) {
				caught = true;
			}
			if (!caught) {
				throw new RuntimeException("inserting into wrong position should throw exception");
			}
			if (!list.isEmpty() || list.size()>0) {
				throw new RuntimeException("inserting into wrong position should not change list size");
			}
		}

		
	}
	
	void runTests() throws TestException {
		testNewInstanceIsEmpty();
		testInsert();
		testRemove();
		testClear();
		testSwap();
                testOutOfBounds();
                testSize();
		System.out.println("TEST PASSED.");
	}
	protected abstract SingleLinkedList newInstance();
}
